package org.an.demo.view;

import android.content.Context;
import android.graphics.Canvas;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * Created by guolin on 16/1/12.
 * 滑动的时候子view并没有重新绘制，只是viewgroup调用了scrollTo或scrollBy方法
 * TODO 实现滑动距离超过指定距离认为是滑动，拦截子类。滑动的时候如何算滑动的距离
 */
public class ScrollerLayout extends ViewGroup {

  /**
   * 用于完成滚动操作的实例
   */
  private Scroller mScroller;

  /**
   * 判定为拖动的最小移动像素数
   */
  private int mTouchSlop;

  /**
   * 界面可滚动的左边界
   */
  private int leftBorder;

  /**
   * 界面可滚动的右边界
   */
  private int rightBorder;

  public ScrollerLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    // 第一步，创建Scroller的实例
    mScroller = new Scroller(context);
    ViewConfiguration configuration = ViewConfiguration.get(context);
    mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    //mTouchSlop = 300;
  }

  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      View childView = getChildAt(i);
      // 为ScrollerLayout中的每一个子控件测量大小
      measureChild(childView, widthMeasureSpec, heightMeasureSpec);
    }
    System.out.println("========= onMeasure ============");
  }

  @Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
    System.out.println("========= onLayout ============");
    if (changed) {
      int childCount = getChildCount();
      for (int i = 0; i < childCount; i++) {
        View childView = getChildAt(i);
        // 为ScrollerLayout中的每一个子控件在水平方向上进行布局
        // 每一个都从最左边开始
        childView.layout(i * getWidth(), 0, i * getWidth() + childView.getMeasuredWidth(),
            childView.getMeasuredHeight());
      }
      // 初始化左右边界值
      leftBorder = getChildAt(0).getLeft();
      //rightBorder = getChildAt(getChildCount() - 1).getRight();
      rightBorder = childCount * getWidth();
    }
  }

  /**
   * 自定义ViewGroup如果没有设置背景并且没有调用setWillNotDraw(false)，就不会回调onDraw方法，invalid会回调 dispatchDraw
   */
  @Override protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
    //System.out.println("========= dispatchDraw ============");
  }

  /**
   * onInterceptTouchEvent方法返回true,后续的事件就会被转移到该ViewGroup的onTouchEvent方法进行处理，
   * 而在后续的事件处理过程中onInterceptTouchEvent中也不会收到后续事件
   *
   * 事件处理（即onTouchEvent是冒泡的），如果不拦截，那么事件就往下走，子view（这里只考虑子view不再是ViewGroup）
   * 如果处理了（事件就被消费掉了），就不会再向上传了。ViewGroup也就不会回调onTouchEvent方法了，
   * 如果子view也没有处理，事件就会向上传递，ViewGroup回调onTouchEvent方法，如果处理就消费掉了，否则继续向上（最上层为Activity）传递
   *
   * 当手指拖动值大于TouchSlop值时，认为应该进行滚动，拦截子控件的事件
   */
  @Override public boolean onInterceptTouchEvent(MotionEvent ev) {
    System.out.println("onTouchEvent = " + MyLinearLayout.getAction(ev));
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        mLastX = ev.getX();
        mLastDownX = mLastX;
        return false;
      case MotionEvent.ACTION_MOVE:
        // 当手指拖动值大于TouchSlop值时，认为应该进行滚动，拦截子控件的事件
        // 这里不能直接return true，否则子view无法响应点击事件（点击事件是在up的时候触发的）
        // 如果我们把mTouchSlop调整的很大，那么分析一下
        // 1.down事件onInterceptTouchEvent不拦截，子view消费，然后move事件，子view消费（由于这里很大），
        // 如果滑动的距离没有超过TouchSlop，不拦截所以move事件都是子view处理的，
        // 只要没有超过TouchSlop，onInterceptTouchEvent一直不拦截，move事件都会经onInterceptTouchEvent
        // 最后up事件，子view响应点击事件
        // 如果某次move超过了，被拦截，则viewgroup onTouchEvent处理move事件

        mLastX = ev.getX();
        System.out.println("onInterceptTouchEvent move = " + mLastX);
        float diff = Math.abs(mLastX - mLastDownX);
        if (diff > mTouchSlop) {
          return true;
        }
        return false;
    }
    return super.onInterceptTouchEvent(ev);
  }

  private float mLastX;
  private float mLastDownX;

  @Override public boolean dispatchTouchEvent(MotionEvent ev) {
    return super.dispatchTouchEvent(ev);
  }

  @Override public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
      // 如果down事件传递到这里，也就意味着子view没有消费，那么这里必须消费，否则后续事件就不会再处理，也就不能move了
      case MotionEvent.ACTION_DOWN:
        return true;
      case MotionEvent.ACTION_MOVE:
        float x = event.getX();
        int scrolledX = (int) (mLastX - x);
        mLastX = x;
        // 如果滑动距离超过边界就scrollTo到边界（scrollTo内部处理已经在边界的话就不会走了）
        if (getScrollX() + scrolledX < leftBorder) {
          scrollTo(leftBorder, 0);
          return true;
        } else if (getScrollX() + scrolledX > rightBorder - getWidth()) {
          // 最多只能滑到 rightBorder - getWidth()
          scrollTo(rightBorder - getWidth(), 0);
          return true;
        }
        scrollBy(scrolledX, 0);
        break;
      case MotionEvent.ACTION_UP:
        // 当手指抬起时，根据当前的滚动值来判定应该滚动到哪个子控件的界面
        int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
        //  这个是滑动的距离
        int dx = targetIndex * getWidth() - getScrollX();
        // 第二步，调用startScroll()方法来初始化滚动数据并刷新界面，
        // Scroller只是个计算器，提供插值计算，从getScrollX()变到0的动画
        mScroller.startScroll(getScrollX(), 0, dx, 0);
        invalidate();
        break;
    }
    return super.onTouchEvent(event);
  }

  /**
   * 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
   * 在draw的时候调用，scrollTo 和scrollBy都会引起invalidate
   */
  @Override public void computeScroll() {
    // 这里判断动画没有结束（mScroller.computeScrollOffset()返回true），就读取当前的值，然后scrollTo到该值
    // 其实说白了就是利用scroller的动画插值器的变化（调用mScroller.startScroll），
    // 在view重绘（父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制）的时候（会调用computeScroll方法）
    // 滚动到scroller的位置
    if (mScroller.computeScrollOffset()) {
      scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
      // 这里必须调用invalidate，因为虽然scrollTo内部会调用invalidate，
      // 但是如果scrollTo 的位置与当前位置一样就会导致导致不会再invalidate，
      // 导致无法在回调computeScroll，所以就没法动了
      invalidate();
    }
  }

  @Override protected void onDetachedFromWindow() {
    // To be on the safe side, abort the scroller
    if ((mScroller != null) && !mScroller.isFinished()) {
      mScroller.abortAnimation();
    }
    super.onDetachedFromWindow();
  }
}